﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

// FIXME double-check exceptions (text double?)

namespace VEXREDUS_hil_interface
{
    class VxSerialMessage
    {
        #region Attributes
        private string rawData;
        public string RawData { get { return rawData; } }

        private char msgType;
        public char MsgType { get { return msgType; } }

        // FIXME packet class only works for data fields with ONLY longs
        private long[] vars;
        public long[] Vars { get { return vars; } }
        #endregion

        // FIXME abstract this part
        #region protocol_settings
        private const char protocol_SOM = '$'; // Start-of-message
        private const char protocol_EOM = '\n'; // End-of-message
        private const char protocol_delim = ','; // Variable delimter
        private const char protocol_CRCdelim = '*'; // Checksum delimiter

        private const int protocol_CRC_length = 2;
        private const int protocol_msg_length_min = 1+1+3+1+2+1; // SOM+msgtype+msg+CRCdelim+CRC+EOM
        #endregion

        #region Contructor
        // Construct message by decoding raw input data (INCOMING)
        public VxSerialMessage(string rawData)
        {
            this.rawData = rawData;

            DecodeRawInput();
        }

        // Constcut message by structured input data (OUTGOING)
        public VxSerialMessage(char msgType, long[] vars)
        {
            this.msgType = msgType;
            this.vars = vars;

            EncodeStructuredInput();
        }
        
        #endregion

        #region Methods
        private void EncodeStructuredInput()
        {
            try
            {
                // Header: SOM + msgType
                rawData = protocol_SOM.ToString() + msgType.ToString();

                // Data fields
                for (int i = 0; i < vars.Length; i++)
                {
                    rawData += vars[i].ToString();
                    if (i != (vars.Length - 1))
                    {
                        rawData += protocol_delim.ToString();
                    }
                }

                // Tail: Checksum delimiter + Checksum + EOM
                rawData += protocol_CRCdelim.ToString() + calculate_checksum(rawData) + protocol_EOM.ToString();

            }
            catch (Exception ex)
            {
                throw new System.ArgumentException("VxSerialMessage: EncodeStructuredInput(): " + ex.Message);
            }
        }

        private void DecodeRawInput()
        {
            string crc, msg, msg_delimvars;
            string[] vars_s;

            try
            {
                // Expects the raw string to one complete message: SOM [message]
                // Example: $c323,-234*FF\n

                /*
                 * Check/validate msg format
                 */

                // Check message minimum length
                if (rawData.Length < protocol_msg_length_min)
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): Message shorter than minimum allowed length, message = \"" + rawData + "\"");
                }

                // Check SOM
                if (rawData[0] != protocol_SOM)
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): First character isn't SOM.");
                }

                // Check EOM
                if (rawData[rawData.Length - 1] != protocol_EOM)
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): Last character isn't EOM.");
                }

                // Check CRC
                if (rawData[rawData.Length - 4] != protocol_CRCdelim)
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): Message contains no CRC delimiter.");
                }

                //crc = rawData.Substring((rawData.Length - 3), 2);
                string[] msg_crc = rawData.Split(protocol_CRCdelim);
                if (msg_crc.Length != 2)
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): Can't split msg/crc.");
                }

                if (msg_crc[1].Length != 3)
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): CRC+EOM is wrong length.");
                }

                msg = msg_crc[0]; // Keep SOM and msgtype for checksum calc
                crc = msg_crc[1].Substring(0, 2); // Cut away EOM form checksum calc
                
                if (crc != calculate_checksum(msg))
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): Checksum calculation failed, received CRC =\"" + crc + "\", correct CRC =\"" + calculate_checksum(crc) + "\"");
                }
                
                if (msg.Length < 5)
                {
                    throw new System.Exception("VxSerialMessage: DecodeRawInput(): MSG (without CRC) is too short.");
                }

                /*
                 * Decode message
                 */

                this.msgType = msg[1];

                msg_delimvars = msg.Substring(2, msg.Length - 2); // Remove SOM and msgtype
                vars_s = msg_delimvars.Split(protocol_delim); // Split by delimiter 
                this.vars = new long[vars_s.Length];
                
                for(int i=0;i<vars_s.Length;i++)
                {
                    this.vars[i] = Int64.Parse(vars_s[i]);
                }
                
            }
            catch (Exception ex)
            {
                throw new System.ArgumentException("VxSerialMessage: DecodeRawInput(): message = \"" + rawData + "\", " + ex.Message);
            }
        }
        #endregion

        #region CRC
        // FIXME move these to its own class later
        // FIXME give proper names
        private string calculate_checksum(string msg)
        {
            //Console.WriteLine("calculate checksum - in msg: " + msg + "#####");
            byte crc = 0;

            for (int i = 0; i < msg.Length; i++)
            {
                crc = _crc_ibutton_update(crc, (byte)msg[i]);
            }
            //Console.WriteLine("calculate checksum - return: " + crc.ToString("X2"));
            return crc.ToString("X2");

        }


        static byte _crc_ibutton_update(int crc, byte data)
        {
            //byte i;
            crc = crc ^ data;
            for (int i = 0; i < 8; i++)
            {
                if ((crc & 0x01) == 0x01)
                {
                    crc = (crc >> 1) ^ 0x8C;
                }
                else
                {
                    crc >>= 1;
                }
            }

            return Convert.ToByte(crc);
        }
        #endregion
    }
}
